package biz

import (
	"context"
	"time"

	"github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"gorm.io/gorm"

	pb "gitee.com/danqingzhao/go-dango/api/user/role/v1"
	"gitee.com/danqingzhao/go-dango/pkg/common/core"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
	"gitee.com/danqingzhao/go-dango/pkg/third/orm"
)

type RoleModel struct {
	Id          uint32            `gorm:"column:id;primary_key;AUTO_INCREMENT;comment:编号" json:"id"`
	CreatedAt   time.Time         `gorm:"column:create_time;comment:创建时间" json:"created_at"`
	UpdatedAt   time.Time         `gorm:"column:update_time;comment:修改时间" json:"updated_at"`
	DeleteAt    gorm.DeletedAt    `gorm:"column:delete_at;type:boolean;comment:是否删除" json:"delete_at"`
	Name        string            `gorm:"column:name;type:varchar(50);not null;uniqueIndex;comment:名称" json:"name"`
	IsActive    bool              `gorm:"column:is_active;type:boolean;comment:是否激活" json:"is_active"`
	Descr       string            `gorm:"column:descr;type:varchar(254);comment:描述" json:"descr"`
	Permissions []PermissionModel `gorm:"many2many:auth_role_permission;joinForeignKey:role_id;joinReferences:permission_id;constraint:OnDelete:CASCADE"`
	Menus       []MenuModel       `gorm:"many2many:auth_role_menu;joinForeignKey:role_id;joinReferences:menu_id;constraint:OnDelete:CASCADE"`
	Buttons     []ButtonModel     `gorm:"many2many:auth_role_button;joinForeignKey:role_id;joinReferences:button_id;constraint:OnDelete:CASCADE"`
}

func (m *RoleModel) TableName() string {
	return "user_role"
}

type RoleRepo interface {
	CreateModel(context.Context, *RoleModel) error
	UpdateModel(context.Context, RoleModel, map[string]any, ...any) error
	DeleteModel(context.Context, ...any) error
	FindModel(context.Context, []string, ...any) (*RoleModel, error)
	ListModel(context.Context, orm.QueryParams) (int64, []RoleModel, error)
	ListPolicies(context.Context, RoleModel) ([][]string, [][]string)
}

type RoleUsecase struct {
	log        *log.Helper
	permRepo   PermissionRepo
	menuRepo   MenuRepo
	buttonRepo ButtonRepo
	roleRepo   RoleRepo
	policyRepo PolicyRepo
}

func NewRoleUsecase(
	logHelpers *settings.LogHelpers,
	permRepo PermissionRepo,
	menuRepo MenuRepo,
	buttonRepo ButtonRepo,
	roleRepo RoleRepo,
	policyRepo PolicyRepo,
) *RoleUsecase {
	return &RoleUsecase{
		log:        logHelpers.Biz,
		permRepo:   permRepo,
		menuRepo:   menuRepo,
		buttonRepo: buttonRepo,
		roleRepo:   roleRepo,
		policyRepo: policyRepo,
	}
}

func (uc *RoleUsecase) association(
	ctx context.Context,
	m RoleModel,
	permIds []uint32,
	menuIds []uint32,
	buttonIds []uint32,
) (*RoleModel, *errors.Error) {
	const query = "id in ?"
	qp := orm.QueryParams{
		Preloads: []string{},
		OrderBy:  []string{"id"},
		IsCount:  false,
		Limit:    0,
		Offset:   0,
	}
	qp.Query = map[string]any{query: permIds}
	_, pms, pErr := uc.permRepo.ListModel(ctx, qp)
	if pErr != nil {
		rErr := core.NewGormError(pErr, pb.ErrorReason_ROLE_LIST_PERMISSION_ERROR.String(), nil)
		rErr.Reason = pb.ErrorReason_ROLE_LIST_PERMISSION_ERROR.String()
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if len(pms) > 0 {
		m.Permissions = pms
	}
	qp.Query = map[string]any{query: menuIds}
	_, mms, mErr := uc.menuRepo.ListModel(ctx, qp)
	if mErr != nil {
		rErr := core.NewGormError(mErr, pb.ErrorReason_ROLE_LIST_MENU_ERROR.String(), nil)
		rErr.Reason = pb.ErrorReason_ROLE_LIST_PERMISSION_ERROR.String()
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if len(mms) > 0 {
		m.Menus = mms
	}
	qp.Query = map[string]any{query: buttonIds}
	_, bms, bErr := uc.buttonRepo.ListModel(ctx, qp)
	if bErr != nil {
		rErr := core.NewGormError(bErr, pb.ErrorReason_ROLE_LIST_BUTTON_ERROR.String(), nil)
		rErr.Reason = pb.ErrorReason_ROLE_LIST_PERMISSION_ERROR.String()
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if len(bms) > 0 {
		m.Buttons = bms
	}
	return &m, nil
}

func (uc *RoleUsecase) CreateRole(
	ctx context.Context,
	permIds []uint32,
	menuIds []uint32,
	buttonIds []uint32,
	m RoleModel,
) (*RoleModel, *errors.Error) {
	am, aErr := uc.association(ctx, m, permIds, menuIds, buttonIds)
	if aErr != nil {
		return nil, aErr
	}
	if err := uc.roleRepo.CreateModel(ctx, am); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_ROLE_CREATE_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	pRules, gRules := uc.roleRepo.ListPolicies(ctx, *am)
	if err := uc.policyRepo.AddPolicies(ctx, pRules, gRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_ROLE_ADD_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return am, nil
}

func (uc *RoleUsecase) UpdateRoleById(
	ctx context.Context,
	roleId uint32,
	permIds []uint32,
	menuIds []uint32,
	buttonIds []uint32,
	im RoleModel,
) (*RoleModel, *errors.Error) {
	am, aErr := uc.association(ctx, im, permIds, menuIds, buttonIds)
	if aErr != nil {
		return nil, aErr
	}
	ummap := make(map[string]any)
	if am.Permissions != nil {
		ummap["Permissions"] = am.Permissions
	}
	if am.Menus != nil {
		ummap["Menus"] = am.Menus
	}
	if am.Buttons != nil {
		ummap["Buttons"] = am.Buttons
	}
	m, fErr := uc.FindRoleByID(ctx, []string{"Permissions", "Menus", "Buttons"}, roleId)
	if fErr != nil {
		return nil, fErr
	}
	pOldRules, gOldRules := uc.roleRepo.ListPolicies(ctx, *m)
	if err := uc.roleRepo.UpdateModel(ctx, *am, ummap, "id = ?", roleId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_ROLE_UPDATE_FAILED.String(), core.GenerateTempDataByID(roleId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	pNewRules, gNewRules := uc.roleRepo.ListPolicies(ctx, im)
	if err := uc.policyRepo.RemovePolicies(ctx, pOldRules, gOldRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_ROLE_REMOVE_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	if err := uc.policyRepo.AddPolicies(ctx, pNewRules, gNewRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_ROLE_ADD_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return uc.FindRoleByID(ctx, []string{"Permissions", "Menus", "Buttons"}, roleId)
}

func (uc *RoleUsecase) DeleteRoleById(ctx context.Context, roleId uint32) *errors.Error {
	m, fErr := uc.FindRoleByID(ctx, []string{"Permissions", "Menus", "Buttons"}, roleId)
	if fErr != nil {
		return fErr
	}
	pRules, gRules := uc.roleRepo.ListPolicies(ctx, *m)
	if err := uc.roleRepo.DeleteModel(ctx, roleId); err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_ROLE_DELETE_FAILED.String(), core.GenerateTempDataByID(roleId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	if err := uc.policyRepo.RemovePolicies(ctx, pRules, gRules); err != nil {
		rErr := errors.InternalServer(pb.ErrorReason_ROLE_REMOVE_POLICY_ERROR.String(), err.Error()).WithCause(err)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return rErr
	}
	return nil
}

func (uc *RoleUsecase) FindRoleByID(
	ctx context.Context,
	preloads []string,
	roleId uint32,
) (*RoleModel, *errors.Error) {
	m, err := uc.roleRepo.FindModel(ctx, preloads, roleId)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_ROLE_FIND_FAILED.String(), core.GenerateTempDataByID(roleId))
		uc.log.WithContext(ctx).Error(rErr.Error())
		return nil, rErr
	}
	return m, nil
}

func (uc *RoleUsecase) ListRole(
	ctx context.Context,
	page, size int,
	query map[string]any,
) (int64, []RoleModel, *errors.Error) {
	qp := orm.QueryParams{
		Preloads: []string{},
		Query:    query,
		OrderBy:  []string{"id"},
		Limit:    max(size, 0),
		Offset:   max(page-1, 0),
		IsCount:  true,
	}
	count, ms, err := uc.roleRepo.ListModel(ctx, qp)
	if err != nil {
		rErr := core.NewGormError(err, pb.ErrorReason_ROLE_LIST_FAILED.String(), nil)
		uc.log.WithContext(ctx).Error(rErr.Error())
		return 0, nil, rErr
	}
	return count, ms, nil
}
